#include "stdafx.h"
#include "MessageReplyTestSuite.h"
#include "TestActiveObject.h"

namespace CO { namespace SpiFoundation { namespace RoomFW {

using namespace ErrorHandling;
using namespace SpiControl::Protocol;

MessageReplyTestSuite::MessageReplyTestSuite(void)
{
	LOG_TEST_ADD(MessageReplyTestSuite::invokeSuccess)
	LOG_TEST_ADD(MessageReplyTestSuite::invokeTimeout)
}

void MessageReplyTestSuite::setup()
{
	LoggedTestSuite::setup();
}

void MessageReplyTestSuite::tear_down()
{
	LoggedTestSuite::tear_down();
}

//
// Message Sender message handler.
//
// - Bind a portal to PORT_MY_PORT_1 for test control.
//	- Send IN_INT_MESSAGE1 to start a test.  Parameter is invoke() timeout.  0 = sendMessage().
//	- When test is complete it will send OUT_INT_MESSAGE1.  Parameter is bool = received reply.
//
// - Bind a Receiver to PORT_MY_PORT_3.
//	- The test consists of either sending or invoking an IN_VOID_MESSAGE1 message out PORT_MY_PORT_3
//	and if and when a reply is received or invoke times out, sending a response out PORT_MY_PORT_1.
//
class Sender : public ActiveObject::MessageHandler
{
public:

	// Shared pointer typedef.
	typedef boost::shared_ptr<Sender> Ptr;

	// Constructor.
	Sender(ActiveObject* parent) :
	  ActiveObject::MessageHandler(parent)
	{}

	// Message handler.
	virtual bool handle(RoomMessageSP message)
	{
		switch (message->portIndex().port())
		{
		// Test control port.
		case TestActiveInterface::PORT_MY_PORT_1:
			{
				switch (message->messageID())
				{
				case GenericTestProtocol::IN_INT_MESSAGE1: // IN_INT_MESSAGE1 always starts the test.
					{
						IntPayloadSP payload = boost::dynamic_pointer_cast<IntPayload, IPayload>(message->releasePayload());
						if (0 != payload)
						{
							DWORD timeout = static_cast<DWORD>(payload->value());
							if (0 == timeout)
							{
								this->sendMessage(TestActiveInterface::PORT_MY_PORT_3, GenericTestProtocol::IN_VOID_MESSAGE1);
							}
							else
							{
								RoomMessageSP reply = this->invoke(timeout, TestActiveInterface::PORT_MY_PORT_3, GenericTestProtocol::IN_VOID_MESSAGE1);
								bool receivedReply = (0 != reply);
								IntPayloadSP replyPayload(new IntPayload(receivedReply));
								this->sendMessage(TestActiveInterface::PORT_MY_PORT_1,
												  GenericTestProtocol::OUT_INT_MESSAGE1,
												  IPayload::ReleaseOwnership(replyPayload));
							}
						}
					}
					break;
				default:
					ASSERT(false);
					break;
				}
			}
			break;
		case TestActiveInterface::PORT_MY_PORT_3:
			{
				switch (message->messageID())
				{
				case GenericTestProtocol::OUT_VOID_MESSAGE1:
					{
						bool receivedReply = true;
						IntPayloadSP replyPayload(new IntPayload(receivedReply));
						this->sendMessage(TestActiveInterface::PORT_MY_PORT_1,
										  GenericTestProtocol::OUT_INT_MESSAGE1,
										  IPayload::ReleaseOwnership(replyPayload));
					}
					break;
				default:
					ASSERT(FALSE); // Already handled all legal messages.  Anything else is an error!
				}
			}
			break;
		default:
			ASSERT(FALSE); // No more ports!
		}
		return true;
	}
};

//
// Message Receiver message handler.
//
// - Pass configuration to constructor: 1) Delay before reply.  2)  Expected return value from reply.
// - Bind a Sender to PORT_MY_PORT_1.
//
class Receiver : public ActiveObject::MessageHandler
{
public:

	// Shared pointer typedef.
	typedef boost::shared_ptr<Receiver> Ptr;

	// Constructor.
	Receiver(ActiveObject* parent, DWORD replyTimeout, bool expectedReply) :
	  ActiveObject::MessageHandler(parent),
	  replyTimeout_(replyTimeout),
	  expectedReply_(expectedReply),
	  actualReply_(!expectedReply)
	{}

    // Check if actual reply equals expected.
	bool passed()
	{
		return (expectedReply_ == actualReply_);
	}

	// Message handler.
	virtual bool handle(RoomMessageSP message)
	{
		switch (message->portIndex().port())
		{
		// Test control port.
		case TestActiveInterface::PORT_MY_PORT_1:
			{
				switch (message->messageID())
				{
				case GenericTestProtocol::IN_VOID_MESSAGE1:
					{
						if (replyTimeout_ > 0)
						{
							::Sleep(replyTimeout_);
						}
						actualReply_ = message->reply(GenericTestProtocol::OUT_VOID_MESSAGE1);
					}
					break;
				default:
					ASSERT(false);
					break;
				}
			}
			break;
		default:
			ASSERT(FALSE); // No more ports!
		}
		return true;
	}
private:
	DWORD replyTimeout_;
	const bool expectedReply_;
	bool actualReply_;
};

void MessageReplyTestSuite::invokeSuccess(void)
{
	// Create Sender and Receiver ActiveObjects and MessageHandlers
	TestActiveObjectSP senderObj(new TestActiveObject("senderObj"));
	Sender::Ptr senderMH(new Sender(senderObj.get()));
	senderObj->mapMessageHandler(TestActiveInterface::PORT_MY_PORT_1, senderMH);
	TestActiveObjectSP receiverObj(new TestActiveObject("receiverObj"));
	Receiver::Ptr receiverMH(new Receiver(receiverObj.get(), 0, true));
	receiverObj->mapMessageHandler(TestActiveInterface::PORT_MY_PORT_1, receiverMH);

	// Create test portal and bindings.
	EventPortalSP senderPortal(new EventPortal("senderPortal"));
	TEST_ASSERT(senderPortal->initialize());
	BindingList bindings;
	bindings.push_back(PortalBindingSP(new PortalBinding(senderPortal, senderObj, TestActiveInterface::PORT_MY_PORT_1)));
	bindings.push_back(ActiveObjectsBindingSP(new ActiveObjectsBinding(senderObj, TestActiveInterface::PORT_MY_PORT_3,
																	   receiverObj, TestActiveInterface::PORT_MY_PORT_1)));

	// Run the test.
	TEST_ASSERT(senderObj->start(DWait));
	TEST_ASSERT(receiverObj->start(DWait));
	int timeout = static_cast<int>(DWait/2);
	IntPayloadSP payload(new IntPayload(timeout));
	senderPortal->sendMessage(GenericTestProtocol::IN_INT_MESSAGE1, IPayload::ReleaseOwnership(payload));
	TEST_ASSERT(senderPortal->wait(DWait));
	RoomMessageSP reply = senderPortal->getMessage();
	TEST_ASSERT(0 != reply);
	payload = ::boost::dynamic_pointer_cast<IntPayload, IPayload>(reply->releasePayload());
	TEST_ASSERT(0 != payload);
	TEST_ASSERT(payload->value());
	TEST_ASSERT(senderObj->stop(DWait));
	TEST_ASSERT(receiverObj->stop(DWait));
	TEST_ASSERT(receiverMH->passed());
}

void MessageReplyTestSuite::invokeTimeout(void)
{
	// Create Sender and Receiver ActiveObjects and MessageHandlers
	TestActiveObjectSP senderObj(new TestActiveObject("senderObj"));
	Sender::Ptr senderMH(new Sender(senderObj.get()));
	senderObj->mapMessageHandler(TestActiveInterface::PORT_MY_PORT_1, senderMH);
	TestActiveObjectSP receiverObj(new TestActiveObject("receiverObj"));
	Receiver::Ptr receiverMH(new Receiver(receiverObj.get(), (DWait*3/4), false));
	receiverObj->mapMessageHandler(TestActiveInterface::PORT_MY_PORT_1, receiverMH);

	// Create test portal and bindings.
	EventPortalSP senderPortal(new EventPortal("senderPortal"));
	TEST_ASSERT(senderPortal->initialize());
	BindingList bindings;
	bindings.push_back(PortalBindingSP(new PortalBinding(senderPortal, senderObj, TestActiveInterface::PORT_MY_PORT_1)));
	bindings.push_back(ActiveObjectsBindingSP(new ActiveObjectsBinding(senderObj, TestActiveInterface::PORT_MY_PORT_3,
																	   receiverObj, TestActiveInterface::PORT_MY_PORT_1)));

	// Run the test.
	TEST_ASSERT(senderObj->start(DWait));
	TEST_ASSERT(receiverObj->start(DWait));
	int timeout = static_cast<int>(DWait/2);
	IntPayloadSP payload(new IntPayload(timeout));
	senderPortal->sendMessage(GenericTestProtocol::IN_INT_MESSAGE1, IPayload::ReleaseOwnership(payload));
	TEST_ASSERT(!senderPortal->wait(DWait));
	RoomMessageSP reply = senderPortal->getMessage();
	TEST_ASSERT(0 == reply);
	TEST_ASSERT(senderObj->stop(DWait));
	TEST_ASSERT(receiverObj->stop(DWait));
	TEST_ASSERT(receiverMH->passed());
}

}}}
